%{ open Ast %}

%token SEMI LPAREN RPAREN LBRACE LARRAY RARRAY LMAP RMAP RBRACE COMMA
%token PLUS MINUS TIMES DIVIDE ASSIGN RASSIGN
%token EQ NEQ LT LEQ GT GEQ
%token NOT AND OR TRUE FALSE MAYBE
%token FUNCTION RETURN IF THEN ELSE FOREACH IN WHILE BREAK END USE BOOL INT REAL STRING MAP ARRAY HTML
%token <int> LITERAL_INT
%token <float> LITERAL_REAL
%token <bool> LITERAL_BOOL
%token <string> ID
%token <string> LITERAL_STR
%token EOF

%nonassoc NOELSE
%nonassoc ELSE

%left NOT AND OR
%left ASSIGN
%left EQ NEQ
%left LT GT LEQ GEQ
%left PLUS MINUS
%left TIMES DIVIDE

%start program
%type <Ast.program> program

%%

program:
   stmt_list        { List.rev $1 }

stmt_list:
   /* nothing */  { [] }
 | stmt_list stmt { $2 :: $1 }

stmt:
    LBRACE stmt_list RBRACE                      { Block(List.rev $2) }
  | expr SEMI                                    { Expr($1) }
  | RETURN expr SEMI                             { Return($2) }
  | USE LITERAL_STR SEMI                         { Use($2) }
  | BREAK SEMI                                   { Break }
  | IF LPAREN expr RPAREN THEN stmt %prec NOELSE { If($3, $6, Block([])) }
  | IF LPAREN expr RPAREN THEN stmt ELSE stmt    { If($3, $6, $8) }
  | FOREACH LPAREN ID ID IN expr RPAREN stmt     { Foreach($3, $4, $6, $8) }
  | WHILE LPAREN expr RPAREN stmt                { While($3, $5) }
  | vartype ID SEMI                              { Declare($1,$2) }
  | vartype ID ASSIGN expr SEMI                  { DeclareAssign($1,$2,$4) }
  | FUNCTION ID LPAREN formals_opt RPAREN RASSIGN vartype stmt
       { FuncDecl($2, $4, $7, $8)  }

expr:
    LITERAL                       { Literal($1) }
  | varref LARRAY expr RARRAY ASSIGN expr     { Arrayassign($1,$3, $6) }
  | varref LMAP expr RMAP ASSIGN expr         { Mapassign($1, $3, $6) }
  | varref LARRAY expr RARRAY     { Arrayref($1,$3) }
  | varref LMAP expr RMAP         { Mapref($1, $3) }
  | varref                        { Var($1) }
  | expr PLUS   expr              { Binop($1, Add,   $3) }
  | expr MINUS  expr              { Binop($1, Sub,   $3) }
  | expr TIMES  expr              { Binop($1, Mult,  $3) }
  | expr DIVIDE expr              { Binop($1, Div,   $3) }
  | expr EQ     expr              { Binop($1, Equal, $3) }
  | expr NEQ    expr              { Binop($1, Neq,   $3) }
  | expr LT     expr              { Binop($1, Less,  $3) }
  | expr LEQ    expr              { Binop($1, Leq,   $3) }
  | expr GT     expr              { Binop($1, Greater,  $3) }
  | expr GEQ    expr              { Binop($1, Geq,   $3) }
  | expr AND    expr              { Binop($1, And,   $3) }
  | expr OR     expr              { Binop($1, Or,    $3) }
  | NOT expr                      { Uniop(Not, $2)       }
  | varref ASSIGN expr            { Assign($1, $3) }
  | ID LPAREN actuals_opt  RPAREN { Call($1, $3) }
  | LPAREN expr RPAREN            { $2 }

LITERAL:
    LITERAL_INT    { Literal_int($1)   }
  | LITERAL_REAL   { Literal_float($1) }
  | LITERAL_BOOL   { Literal_bool($1)  }
  | LITERAL_STR    { Literal_str($1)   }
  | LITERAL_array                 { Literal_array($1) }
  | LITERAL_map                   { Literal_map($1)   }

LITERAL_array:
    ARRAY LBRACE RBRACE { [] }
  | ARRAY LBRACE array_list RBRACE {List.rev $3 }

array_list:
    LITERAL  { [$1] }
  | array_list COMMA LITERAL { $3 :: $1}

LITERAL_map:
    MAP LBRACE RBRACE { [] }
  | MAP LBRACE map_list RBRACE {List.rev $3 }

map_list:
    LITERAL RASSIGN LITERAL { [($1, $3)] }
  | map_list COMMA LITERAL RASSIGN LITERAL { ($3, $5) :: $1 }

primtype:
    INT    { Type_int }
  | BOOL   { Type_bool }
  | STRING { Type_str }
  | REAL   { Type_float }
  | HTML   { Type_html }

vartype:
    vartype LARRAY RARRAY    { Type_Array($1) }
  | vartype LMAP vartype RMAP            { Type_Map($1,$3) }
  | primtype { $1 }

varref:
    ID                           { IdVar($1) }

formals_opt:
    /* nothing */ { [] }
  | formals_list  { List.rev $1 }

formals_list:
    vartype ID                     { [($1,$2)] }
  | formals_list COMMA vartype ID  { ($3, $4) :: $1 }

actuals_opt:
    /* nothing */ { [] }
  | actuals_list  { List.rev $1 }

actuals_list:
    expr                     { [$1] }
  | actuals_list COMMA expr  { $3 :: $1 }
